package com.john.http.core.parser;

import com.john.http.core.parser.config.HtmlParseConfig;
import com.john.http.core.parser.config.HtmlParseConfigItem;
import com.john.http.core.util.SimpleBeanUtil;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedList;
import java.util.List;

/**
 * 〈一句话功能简述〉<br/>
 * 〈抽象的HTML解释器〉
 * <p>建议重写的方法</p>
 * <ul>
 * <li>isValidBean()方法，用于验证新解析的Bean是否是一个有效的Bean</li>
 * </ul>
 *
 * @author jiangguangtao on 2016/5/20.
 */
public abstract class AbstractHtmlItemParser<T> implements HtmlItemParser<T> {
    private static final Logger log = LoggerFactory.getLogger(AbstractHtmlItemParser.class);
    private ParserCallback<T> parserCallback;
    //解析配置信息
    private HtmlParseConfig parseConfig;

    public AbstractHtmlItemParser(HtmlParseConfig parseConfig) {
        this.parseConfig = parseConfig;
    }

    @Override
    public void setCallback(ParserCallback<T> callback) {
        parserCallback = callback;
    }

    @Override
    public List<T> parseItem(String htmlContent, String baseUri) {
        Document document = Jsoup.parse(htmlContent, baseUri);
        Element ipTable = getDataContainer(document);
        if (null == ipTable) {
            log.warn("未定位到数据容器 {}", getParseConfig().getContainerSelector());
            return null;
        }
        Elements iptrs = getDataRows(ipTable);
        if (null == iptrs || iptrs.size() == 0) {
            log.warn("未找到有效的数据行 {}", getParseConfig().getRowSelector());
            return null;
        }
        int size = iptrs.size();
        List<T> dataList = new LinkedList<>();
        int i = getParseConfig().getHeadRows();
        //第一行标题行不要
        for (; i < size; i++) {
            T pi = parseItem(iptrs.get(i));
            if (isValidBean(pi)) {
                log.debug("解释的Bean信息：{}", pi);
                dataList.add(pi);
                callback(pi);
            }
        }
        return dataList;
    }

    /**
     * 根据配置项中的containerSelector取数据所在的DOM节点
     *
     * @param document
     * @return
     */
    protected Element getDataContainer(Document document) {
        return document.select(getParseConfig().getContainerSelector()).first();
    }

    /**
     * 根据配置项中的rowSelector在窗口中选择行，如果为空，取所有直接的子节点
     *
     * @param ipTable
     * @return
     */
    protected Elements getDataRows(Element ipTable) {
        if (StringUtils.isBlank(getParseConfig().getRowSelector())) {
            return ipTable.children();
        } else {
            return ipTable.select(getParseConfig().getRowSelector());
        }
    }

    /**
     * 验证Bean是否有效
     * <p>默认不为null就是有效的</p>
     *
     * @param bean
     * @return
     */
    protected boolean isValidBean(T bean) {
        return null != bean;
    }

    /**
     * 根据colSelector从数据行DOM节点中取所有的列
     * <p>如果未配置将直接取子节点</p>
     *
     * @param rowElement
     * @return
     */
    protected Elements getRowCols(Element rowElement) {
        if (StringUtils.isBlank(getParseConfig().getColSelector())) {
            return rowElement.children();
        } else {
            return rowElement.select(getParseConfig().getColSelector());
        }
    }

    /**
     * 解析一行数据
     *
     * @param rowEle
     * @return
     */
    private T parseItem(Element rowEle) {
        Elements tds = getRowCols(rowEle);
        T pi = createNewBean();
        for (int i = 0; i < tds.size(); i++) {
            if (getParseConfig().isColMapped(i)) {
                Element td = tds.get(i);
                setFieldValue(pi, i, td, getParseConfig().getConfigItem(i));
            }
        }
        return pi;
    }

    /**
     * 创建一个新的Bean
     *
     * @return
     */
    protected abstract T createNewBean();

    /**
     * 设置列值到对象属性上
     *
     * @param pi          属性保存对象
     * @param colIndex    当前列序号
     * @param cellElement 当前列HTML节点
     * @param itemConfig  配置
     */
    protected void setFieldValue(T pi, int colIndex, Element cellElement, HtmlParseConfigItem itemConfig) {
        Element workElement = cellElement;

        if (StringUtils.isNotBlank(itemConfig.getValueSelector())) {
            workElement = cellElement.select(itemConfig.getValueSelector()).first();
            if (null == workElement) {
                String msg = String.format("通过选择值器%s未找到节点，相关节点：\n%s", itemConfig.getValueSelector(), cellElement.html());
                throw new NullPointerException(msg);
            }
        }

        String value = null;
        if (StringUtils.isNotBlank(itemConfig.getValueAttr())) {
            value = workElement.attr(itemConfig.getValueAttr());
            if (StringUtils.isBlank(value)) {
                log.warn("通过值属性名称{}未找到值", itemConfig.getValueAttr());
            }
        } else {
            value = workElement.text();
        }
        if (StringUtils.isNotBlank(value)) {
            if (null != itemConfig.getValueConvert()) {
                Object objVal = itemConfig.getValueConvert().convert(value);
                SimpleBeanUtil.setValue(pi, itemConfig.getField(), objVal);
            } else {
                SimpleBeanUtil.setValue(pi, itemConfig.getField(), value);
            }
        }
    }

    /**
     * 调用回调方法
     *
     * @param bean
     */
    protected void callback(T bean) {
        if (null != parserCallback) {
            parserCallback.callback(bean);
        }
    }

    /**
     * 获取解析配置信息
     *
     * @return
     */
    public HtmlParseConfig getParseConfig() {
        return parseConfig;
    }
}
